/* Copyright (c) 2010, mitsuhisa.net
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 *       copyright notice, this list of conditions and the following
 *       disclaimer in the documentation and/or other materials provided
 *       with the distribution.
 *     * Neither the name of the misuhisa.net nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <iostream>
#include "Range.hpp"
#include "StrPool.hpp"
#include "String.hpp"
#include "Context.hpp"

namespace nyu {

Range::Range(Value proto, bool is_in, Value start, Value end, Value step) :
        GeneralObject(proto) {
    addSlot(StrPool::get("includeBorder"), int_to_value(is_in), true);
    addSlot(StrPool::get("start"), start, true);
    addSlot(StrPool::get("end"), end, true);
    addSlot(StrPool::get("step"), step, true);
}

Value Range::each(Context& c) {
    Value block = c.currentArgv(1);
    if (!is_object(block) || is_null(block))
        return NULL_VALUE;

    Value vis_in =
            getSlot(object_to_value(StrPool::get("includeBorder", true)), c);
    Value vstart = getSlot(object_to_value(StrPool::get("start", true)), c);
    Value vend = getSlot(object_to_value(StrPool::get("end", true)), c);
    Value vstep = getSlot(object_to_value(StrPool::get("step", true)), c);

    bool is_in = is_int(vis_in) ? value_to_int(vis_in) : is_null(vis_in)
            ? false : value_to_object(vis_in)->isTrue(c);

    int_type start = is_int(vstart) ? value_to_int(vstart) : is_null(vstart)
            ? 0 : value_to_object(vstart)->toIntval(c);

    int_type end = is_int(vend) ? value_to_int(vend) : is_null(vend)
            ? 0 : value_to_object(vend)->toIntval(c);

    int_type step = is_int(vstep) ? value_to_int(vstep) : is_null(vstep)
            ? 0 : value_to_object(vstep)->toIntval(c);

    if (step == 0)
        step = start <= end ? 1 : -1;

    Value ret = NULL_VALUE;
    if (start <= end) {
        if (is_in) {
            for (int_type i = start, j = end; i <= j; i += step) {
                if (is_object(ret) && !is_null(ret))
                    value_to_object(ret)->unref();
                ret = c.call(block, int_to_value(i));
            }
        } else {
            for (int_type i = start, j = end; i < j; i += step) {
                if (is_object(ret) && !is_null(ret))
                    value_to_object(ret)->unref();
                ret = c.call(block, int_to_value(i));
            }
        }
    } else {
        if (is_in) {
            for (int_type i = start, j = end; i >= j; i += step) {
                if (is_object(ret) && !is_null(ret))
                    value_to_object(ret)->unref();
                ret = c.call(block, int_to_value(i));
            }
        } else {
            for (int_type i = start, j = end; i > j; i += step) {
                if (is_object(ret) && !is_null(ret))
                    value_to_object(ret)->unref();
                ret = c.call(block, int_to_value(i));
            }
        }
    }

    return ret;
}

}
